#ifndef _COMPILATION_INSTRUCTION_TRAITS_H_
#define _COMPILATION_INSTRUCTION_TRAITS_H_

#include <compilation/Forward.h>

#include <cassert>


// InstructionType.
namespace compilation
{

enum InstructionType
{
  TInstructionUndefined = 0,
  TExpression,
  TIf,
  TGoto,
  TLabel,
  TReturn,
  TEnterBlock,
  TExitBlock,
  TInstructionLast
};

} // compilation

// instruction_trait and id_to_instruction_type.
namespace compilation
{

#define _COMPILATION_DECLARE_INSTRUCTION_TRAIT(__type, __enum) \
  template <> struct instruction_trait<__type> { \
  static constexpr InstructionType id = __enum;}; \
  template <> struct id_to_instruction_type<__enum> { \
  typedef __type type; }

template <typename _Instruction>
struct instruction_trait {};

template <InstructionType _Type>
struct id_to_instruction_type {};

_COMPILATION_DECLARE_INSTRUCTION_TRAIT(ExpressionInstruction, TExpression);
_COMPILATION_DECLARE_INSTRUCTION_TRAIT(IfInstruction        , TIf        );
_COMPILATION_DECLARE_INSTRUCTION_TRAIT(GotoInstruction      , TGoto      );
_COMPILATION_DECLARE_INSTRUCTION_TRAIT(LabelInstruction     , TLabel     );
_COMPILATION_DECLARE_INSTRUCTION_TRAIT(ReturnInstruction    , TReturn    );
_COMPILATION_DECLARE_INSTRUCTION_TRAIT(EnterBlockInstruction, TEnterBlock);
_COMPILATION_DECLARE_INSTRUCTION_TRAIT(ExitBlockInstruction , TExitBlock );

#undef _COMPILATION_DECLARE_INSTRUCTION_TRAIT

} // compilation

// instruction_cast.
namespace compilation
{

template <InstructionType _Type>
inline typename id_to_instruction_type<_Type>::type*
instruction_cast(VirtualInstruction* instr)
{
  assert(dynamic_cast<typename id_to_instruction_type<_Type>::type*>(instr));
  return static_cast<typename id_to_instruction_type<_Type>::type*>(instr);
}

template <InstructionType _Type>
inline typename id_to_instruction_type<_Type>::type const*
instruction_cast(VirtualInstruction const* instr)
{
  assert(dynamic_cast<typename id_to_instruction_type<_Type>::type const*>(instr));
  return static_cast<typename id_to_instruction_type<_Type>::type const*>(instr);
}

template <InstructionType _Type>
inline typename id_to_instruction_type<_Type>::type&
instruction_cast(VirtualInstruction& instr)
{
  assert(dynamic_cast<typename id_to_instruction_type<_Type>::type*>(&instr));
  return static_cast<typename id_to_instruction_type<_Type>::type&>(instr);
}

template <InstructionType _Type>
inline typename id_to_instruction_type<_Type>::type const&
instruction_cast(VirtualInstruction const& instr)
{
  assert(dynamic_cast<typename id_to_instruction_type<_Type>::type const*>(&instr));
  return static_cast<typename id_to_instruction_type<_Type>::type const&>(instr);
}

} // compilation

// ExpressionType.
namespace compilation
{

enum ExpressionType
{
  TExpressionUndefined = 0,
  TConstant,
  TChar,
  TString,
  TLocalVariable,
  TParameter,
  TGlobalVariable,
  TComparison,
  TUnaryOperator,
  TBinaryOperator,
  TDereference,
  TReference,
  TCast,
  TFunctionCall,
  TAssign,
  TPhi,
  TExpressionLast
};

} // compilation

// expression_trait and id_to_expression_type.
namespace compilation
{

#define _COMPILATION_DECLARE_EXPRESSION_TRAIT(__type, __enum) \
  template <> struct expression_trait<__type> { \
  static constexpr ExpressionType id = __enum;}; \
  template <> struct id_to_expression_type<__enum> { \
  typedef __type type; }

template <typename _Expression>
struct expression_trait {};

template <ExpressionType _Type>
struct id_to_expression_type {};

_COMPILATION_DECLARE_EXPRESSION_TRAIT(ConstantExpression      , TConstant      );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(CharExpression          , TChar          );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(StringExpression        , TString        );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(LocalVariableExpression , TLocalVariable );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(ParameterExpression     , TParameter     );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(GlobalVariableExpression, TGlobalVariable);
_COMPILATION_DECLARE_EXPRESSION_TRAIT(ComparisonExpression    , TComparison    );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(UnaryOperatorExpression , TUnaryOperator );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(BinaryOperatorExpression, TBinaryOperator);
_COMPILATION_DECLARE_EXPRESSION_TRAIT(DereferenceExpression   , TDereference   );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(ReferenceExpression     , TReference     );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(CastExpression          , TCast          );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(FunctionCallExpression  , TFunctionCall  );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(AssignExpression        , TAssign        );
_COMPILATION_DECLARE_EXPRESSION_TRAIT(PhiExpression           , TPhi           );

#undef _COMPILATION_DECLARE_EXPRESSION_TRAIT

} // compilation

// expression_cast.
namespace compilation
{

template <ExpressionType _Type>
inline typename id_to_expression_type<_Type>::type*
expression_cast(VirtualExpression* instr)
{
  assert(dynamic_cast<typename id_to_expression_type<_Type>::type*>(instr));
  return static_cast<typename id_to_expression_type<_Type>::type*>(instr);
}

template <ExpressionType _Type>
inline typename id_to_expression_type<_Type>::type const*
expression_cast(VirtualExpression const* instr)
{
  assert(dynamic_cast<typename id_to_expression_type<_Type>::type const*>(instr));
  return static_cast<typename id_to_expression_type<_Type>::type const*>(instr);
}

template <ExpressionType _Type>
inline typename id_to_expression_type<_Type>::type&
expression_cast(VirtualExpression& instr)
{
  assert(dynamic_cast<typename id_to_expression_type<_Type>::type*>(&instr));
  return static_cast<typename id_to_expression_type<_Type>::type&>(instr);
}

template <ExpressionType _Type>
inline typename id_to_expression_type<_Type>::type const&
expression_cast(VirtualExpression const& instr)
{
  assert(dynamic_cast<typename id_to_expression_type<_Type>::type const*>(&instr));
  return static_cast<typename id_to_expression_type<_Type>::type const&>(instr);
}

} // compilation

#endif // _COMPILATION_INSTRUCTION_TRAITS_H_
